Skip to content

fix(urlMatcherFactory): Decode slashes in string routes #2071

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed

Conversation

christianliebel
Copy link

Slashes in string routes now get decoded (they were only encoded before). Related test was fixed.

The current behavior is the following: Encoding the state parameter hello/world correctly gets encoded to hello%252Fworld, but reading it from the target controller returns hello%2Fworld. This is because the value is recognized as a valid string and therefore isn’t decoded. Fixing the is function of the string type now triggers the decode of slashes.

In addition, the related tests was fixed. The description says that encoding and decoding is tested, however only encoding tests take place.

Slashes in string routes now get decoded (they were only encoded before). Related test was fixed.
@@ -578,7 +579,7 @@ function $UrlMatcherFactory() {
decode: valFromString,
// TODO: in 1.0, make string .is() return false if value is undefined/null by default.
// In 0.2.x, string params are optional by default for backwards compat
is: function(val) { return val == null || !isDefined(val) || typeof val === "string"; },
is: function(val) { return (val == null || !isDefined(val) || typeof val === "string") && (val.toString().indexOf(slashReplacement) === -1); },
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This added condition fails when val is null. I tested it in my project and came out with the following improvement:

is: function(val) { return val == null || !isDefined(val) || (isString(val) && val.indexOf(slashReplacement) === -1); },

What do you think @chliebel?

@christianliebel
Copy link
Author

@piotrd Looks good! I updated the PR.

@piotrd
Copy link

piotrd commented Jul 6, 2015

Great. So if this should be a final fix for #1645, #1973 and #2069 (duplicates of the first one).

@tweinerpq tweinerpq mentioned this pull request Jul 9, 2015
@christianliebel
Copy link
Author

@christopherthielen Are you taking care of this? 😉

@christopherthielen christopherthielen self-assigned this Aug 3, 2015
@christopherthielen christopherthielen added this to the 0.2.16 milestone Aug 3, 2015
@ghost
Copy link

ghost commented Aug 5, 2015

@chliebel thanks for this fix! I've took the liberty to merge it into my fork and use it until the upstream gets updated.

@christianliebel
Copy link
Author

@cssensei You’re welcome! My pleasure. 😃

@ehorodyski
Copy link

I've downloaded the latest version (0.2.15) and still notice that forward-slashes get double encoded / to %252F, once the state is reloaded passing in decoded values in $stateParams. Shouldn't it just encode once and not account for the %?

For instance, http://site.com/, upon $state.reload with {q:'I/O'} becomes http://site.com/?q=I%252FO

@christianliebel
Copy link
Author

@ehorodyski Please note that this fix is scheduled for 0.2.16 and not part of 0.2.15. However, you can fork the ui-router repository and merge this fix branch into it.

@DavideCordella
Copy link

is there a way to decorate the "$UrlMatcherFactory" with its fixed version?
or can anybody pull the fix to the master branch?

@cdauth
Copy link

cdauth commented Dec 18, 2015

@chliebel In the description of the pull request you say that “hello/world correctly gets encoded to hello%252Fworld”. Is there any reason not to encode it as hello%2Fworld?

@christopherthielen
Copy link
Contributor

@chliebel thanks for the PR!

I've done more analysis on the slash encoding. Because the browsers are at liberty to consider a url-encoded string to be the same as a non-encoded string (or not), we can not url-encode the slashes and get 100% bidirectional encode/decoding. I've switched to using tilde as an escape character for encoding slashes. This can be seen here: 02e9866

@christianliebel christianliebel deleted the my-fix-branch branch January 26, 2016 15:41
@christianliebel
Copy link
Author

Sounds fair. 👍

@bblack
Copy link

bblack commented Feb 15, 2016

Because the browsers are at liberty to consider a url-encoded string to be the same as a non-encoded string (or not),

this is very surprising to me as it defeats the purpose of url-encoding. in actuality, which browsers do this?

@christopherthielen
Copy link
Contributor

@bblack you're correct, sorry. My memory conflated two separate things.

In fact, the location service is the cause of all this trouble: http://plnkr.co/edit/YziNoXrk2DiPQ20k8DeT?p=preview

@bblack
Copy link

bblack commented Feb 16, 2016

nice, that jives with what i've seen. i was going to try to dig into it tomorrow; now i don't have to. thanks!

do you know if this is deliberate on their part, or if there's an open github issue for this difficulty with having slashes in route/query params? it would be nice if it were fixed in angular so we don't have to do workarounds like this.

separate question: is there a way i can get ui-router's ~2F encoding method without having to implement it myself, in case it changes in the future?

@christopherthielen
Copy link
Contributor

@bblack now that I have a clearer picture of what's happening, I'd be open to going all the way back to standard uriEncoding. I think we just need to encode the whole URL once before passing to location.url(), and also encode each string-type parameter once using encodeURIComponent.

Are you willing to look at doing that? Otherwise I have too much in my plate to tackle it soon.

@christopherthielen
Copy link
Contributor

You can get ui routers implementation from urlmatcher factory's type

@bblack
Copy link

bblack commented Feb 16, 2016

i can try to take a look sometime later this week (no promises).

@christopherthielen
Copy link
Contributor

let me know if you do work on it; I can offer some additional guidance

@Red-3
Copy link

Red-3 commented Feb 16, 2016

Since it looks like you're actively working on this, I have a problem since upgrading ui-router to 0.2.17 in an app which is using ui-router's HTML5 mode, which I believe is directly related to this. In the main path part of the URL, (not the string route) slashes are getting encoded (with ~2F). Is this because the code is incorrectly encoding URLs parsed in HTML5 mode?
From the discussion above it appears to me that only the fragment part of the URL should be encoded with this change. That is clearly not happening in my case.

Since upgrading ui-router the URL after a state.go() is rewritten in the browser as this:
https://localhost:8484/~2Fdocp~2Fapp~2Fwelcome~2Findex.html/register?access_token=xyz
(Note that it only encodes some of the forward slashes.)
It should be:
https://localhost:8484/docp/app/welcome/index.html/register?access_token=xyz

I have tested other apps after upgrading ui-router not using HTML5 mode and the URLs are written (as expected) without the ~2F encoding.

I tried all versions of ui-router back to 0.2.12, and all of them somehow encode some (but not always all) the slashes in the main part of the URL with either (%2F, %252F or ~2F). I am currently stuck with v 0.2.11 because of this issue.

@christopherthielen
Copy link
Contributor

@Red-3 it's expected since 0.2.12 that slashes in parameters are encoded.

given these states:

parent: { url: '/parent/:parent' }
parent.child: { url: '/:child' }

if this: $state.go('parent.child', { parent: 'foo/bar', child: 'baz/qux' }) resulted in a URL like this: /parent/foo/bar/baz/qux it would be impossible to then parse that URL back to the parameters :parent and :child (You can't tell which slashes are static parts of the url, and which parts are part of the parameter value)

So, in the string parameter type, we encode/decode parameters to generate an unambiguous url that can be encoded and decoded bi-directionally: /parent/foo~2Fbar/baz~2Fqux

You can register a custom type that does not encode/decode, then use it in your parameters:

    $urlMatcherFactoryProvider.type("slug", {
        encode: angular.identity,
        decode: angular.identity
    })
.state("parent", { url: '/parent/{param:slug}' });

@Red-3
Copy link

Red-3 commented Feb 16, 2016

Thanks for your explanation @christopherthielen

I now see where the problem is in my ui-router config.
I used regex matching in the url part, which meant that part of the url was being treated as a parameter, and thus being encoded. With your help, I've been able to work around the issue I was having.

@christopherthielen
Copy link
Contributor

@Red-3 can I see what you ended up with? And also what you started with (the regex)
I'll add it to my mental list of use cases

@Red-3
Copy link

Red-3 commented Feb 16, 2016

@christopherthielen : In this situation, we send a URL out to potential customers via email and want Angular to read the token from it. It's a standard URL format (didn't want to add # in the routes).
So I came up with a pattern match for the HTML5 route like this:

    $locationProvider.html5Mode({enabled: true, requireBase: false});
    $stateProvider.state(
        'welcome', {
            url: '{path:.*}.html?access_token', ...

So on the path: https://localhost:8484/docp/app/welcome/index.html?access_token=xyz
the path param is matched as /docp/app/welcome/index, which was getting encoded in 0.2.17 as ~2Fdocp~2Fapp~2Fwelcome~2Findex
(I turned off requireBase, which is why this param included all those / chars.)
I changed my config to requireBase: true (and added a BASE tag to the HEAD of my HTML page).
With the same matching rule I am now seeing path as /index (still includes a slash).
So I took the slash out of my param pattern and revised my matching rule to:

    $locationProvider.html5Mode({enabled: true, requireBase: true});
    $stateProvider.state(
        'welcome', {
            url: '/{path}.html?access_token', ...

(If our landing page was always called index.html I could avoid using the path param, but in dev mode the landing page is named differently - our build renames it to index.html when it's deployed.)

If you have a better suggestion on how to handle this scenario, I'd love to hear it!
Thanks for your attention to this - it really helped! :)

@christopherthielen
Copy link
Contributor

If you have a better suggestion on how to handle this scenario, I'd love to hear it!

I think my previous comment should work for you, registering a named type that does no special encoding for slashes:

        encode: angular.identity,
        decode: angular.identity,
        pattern: /.+/
    })

$stateProvider.state(
        'welcome', {
            url: '{path:slug}.html?access_token', ...```

Question: 
How did `url: '{path:.*}.html?access_token', ...` match the url `https://localhost:8484/docp/app/welcome/index.html/register?access_token=xyz` (the `/register` in the url is throwing me off)

@Red-3
Copy link

Red-3 commented Feb 16, 2016

Good question!
It's my mistake: the URL should be https://localhost:8484/docp/app/welcome/index.html?access_token=xyz
The /register is a subsequent state. I'll fix it in the previous post so it doesn't throw anyone else off.

@bblack
Copy link

bblack commented Feb 17, 2016

related issue in angular: angular/angular.js#13837

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

8 participants